package com.activenetwork.util;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.support.events.EventFiringWebDriver;
import org.openqa.selenium.support.events.WebDriverEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.activenetwork.schema.environment.ChromeType;
import com.activenetwork.schema.environment.EnvironmentType;
import com.activenetwork.schema.environment.FireFoxType;
import com.activenetwork.schema.environment.HtmlUnitType;
import com.activenetwork.schema.environment.InternetExplorerType;
import com.google.common.base.Optional;

public class WebDriverFactory {
	private static Logger logger = LoggerFactory.getLogger(WebDriverFactory.class);
	private static boolean UntrustedCertificateFlag = true;
	private static List<WebDriverEventListener> list;
	private static Map<String, WebDriverEventListener> registeredMap = new HashMap<String, WebDriverEventListener>();
	
	/**
	 * Get list of listener injected by spring
	 * @return
	 */
	public static List<WebDriverEventListener> getList() {
		return list;
	}
	/**
	 * Injection method required by spring
	 * @param list
	 */
	public static void setList(List<WebDriverEventListener> list) {
		WebDriverFactory.list = list;
	}
	/**
	 * Environment specified certification trust setting, data is from EnvironmentType java bean
	 * @param value
	 */
	public static void setUntrustedCertificateFlag(boolean value){
		UntrustedCertificateFlag = value;
	}
	/**
	 * Main method for create a webdriver instance, browser setting from EnvironmentType java bean
	 * @param browserSetting
	 * @return
	 */
	public static WebDriver create(EnvironmentType.BrowserSetting browserSetting){
		WebDriver driver = null;
		
		String runWhichBrowser = browserSetting.getRunWhichBrowser();
		logger.info("Decide which browser to run from <BrowserSetting> attribute - " + runWhichBrowser);
		
		// Create FF
		if(StringUtils.equalsIgnoreCase(runWhichBrowser, BROWSER.FIREFOX.getName())){
			Optional<FireFoxType> firefoxSetting = Optional.fromNullable(browserSetting.getFireFox());
			if(firefoxSetting.isPresent()){
				driver = create(firefoxSetting.get());
			}else{
				throw new RuntimeException("Browser FF created failed.");
			}
		}
		
		// Create Chrome
		if(StringUtils.equalsIgnoreCase(runWhichBrowser, BROWSER.CHROME.getName())){
			Optional<ChromeType> chromeSetting = Optional.fromNullable(browserSetting.getChrome());
			if(chromeSetting.isPresent()){
				driver = create(chromeSetting.get());
			}else{
				throw new RuntimeException("Browser Chrome created failed.");
			}
		}
		
		// Create IE
		if(StringUtils.equalsIgnoreCase(runWhichBrowser, BROWSER.IE.getName())){
			Optional<InternetExplorerType> ieSetting = Optional.fromNullable(browserSetting.getInternetExplorer());
			if(ieSetting.isPresent()){
				driver = create(ieSetting.get());
			}else{
				throw new RuntimeException("Browser IE created failed.");
			}
		}
		
		// Create HtmlUnit
		if(StringUtils.equalsIgnoreCase(runWhichBrowser, BROWSER.HTMLUNIT.getName())){
			Optional<HtmlUnitType> htmlUnitSetting = Optional.fromNullable(browserSetting.getHtmlUnit());
			if(htmlUnitSetting.isPresent()){
				driver = create(htmlUnitSetting.get());
			}else{
				throw new RuntimeException("Browser HtmlUnit created failed.");
			}
		}
		
		// maximize the driver
		driver.manage().window().maximize();
		
		// wrap to eventfiring browser
		return wrap(driver);
	}
	/**
	 * Default browser creation by some test
	 * @return
	 */
	public static WebDriver create(BROWSER browser){
		WebDriver driver = null;
		if(browser == BROWSER.FIREFOX){
			driver = new FirefoxDriver();
		}else if(browser == BROWSER.CHROME){
			System.setProperty("webdriver.chrome.driver", ClassLoader.getSystemResource("chromedriver.exe").getPath());
			driver = new ChromeDriver(); 
		}else if(browser == BROWSER.IE){
			System.setProperty("webdriver.ie.driver", ClassLoader.getSystemResource("IEDriverServer.exe").getPath());
			driver = new InternetExplorerDriver();
		}else{
			driver = new HtmlUnitDriver();
		}
		
		return wrap(driver);
		
	}
	/**
	 * Create a firefox browser
	 * @param setting
	 * @return
	 */
	private static FirefoxDriver create(FireFoxType setting){
		// firefox bin
		System.setProperty("webdriver.firefox.bin", setting.getBinPath());
		
		// firefox profile
		FirefoxProfile profile = null;
		String profilePath = setting.getProfilePath();
		if(StringUtils.equals(profilePath, "")){
			profile = new FirefoxProfile(); // create a new anonymous profile
		}else{ 
			profile = new FirefoxProfile(new File(profilePath));
		}
		
		//security certificate handling
		
		// By default, when accepting untrusted SSL certificates, 
		// assume that these certificates will come from an untrusted issuer or will be self signed. 
		// Due to limitation within Firefox, it is easy to find out if the certificate has expired or 
		// does not match the host it was served for, but hard to find out if the issuer of the certificate is untrusted.
		
		// By default, it is assumed that the certificates were not be issued from a trusted CA.
		
		// If you are receive an "untrusted site" prompt on Firefox 
		// when using a certificate that was issued by valid issuer, 
		// but has expired or is being served served for a different host (e.g. production certificate served in a testing environment) 
		// set this to false.
		profile.setAssumeUntrustedCertificateIssuer(UntrustedCertificateFlag);
		
		// Sets whether Firefox should accept SSL certificates which have expired, 
		// signed by an unknown authority or are generally untrusted. 
		// This is set to true by default.
		
		//profile.setAcceptUntrustedCertificates(false);
		
		
		//firefox plugin
		if(setting.getPlugin().size() > 0){
			List<FireFoxType.Plugin> plugins = setting.getPlugin();
			for(FireFoxType.Plugin plugin : plugins){
				if(plugin.isLoadPlugin()){ //whether to load this plugin
					try{
						File ext = new File(plugin.getValue());
						profile.addExtension(ext);
					}catch(IOException e){
						logger.info("Unable to load the plugin: " + plugin.getValue());
					}
				}
			}
		}
		
		//firefox preference
		if(setting.getPreference().size() > 0){
			List<FireFoxType.Preference> preferences = setting.getPreference();
			for(FireFoxType.Preference pref : preferences){
				profile.setPreference(pref.getKey(), pref.getValue());
			}
		}
		
		return new FirefoxDriver(profile);
	}
	/**
	 * Create a chrome browser
	 * @param setting
	 * @return
	 */
	private static ChromeDriver create(ChromeType setting){
		
		// chrome driver
		System.setProperty("webdriver.chrome.driver", ClassLoader.getSystemResource("chromedriver.exe").getPath());
		
		// chrome options
		ChromeOptions options = null;
		if(setting.getPreference().size() > 0){
			options = new ChromeOptions();
			List<ChromeType.Preference> preferences = setting.getPreference();
			for(ChromeType.Preference pref : preferences){
				options.addArguments("--"+pref.getKey(), pref.getValue());
			}
		}

		if(options == null){
			return new ChromeDriver();		
		}else{
			return new ChromeDriver(options);	
		}
	}
	/**
	 * Create a IE browser
	 * @param setting
	 * @return
	 */
	private static InternetExplorerDriver create(InternetExplorerType setting){
		// IE driver
		System.setProperty("webdriver.ie.driver", ClassLoader.getSystemResource("IEDriverServer.exe").getPath());
		
		// preference
		// no use
		
		return new InternetExplorerDriver();
	}
	/**
	 * Create a HtmlUnit driver
	 * @param setting
	 * @return
	 */
	private static HtmlUnitDriver create(HtmlUnitType setting){
		return new HtmlUnitDriver();
	}
	/**
	 * Wrap all the listeners injected by the spring
	 * @param driver
	 * @return
	 */
	public static WebDriver wrap(WebDriver driver){
		EventFiringWebDriver eventFiringWebDriver = new EventFiringWebDriver(driver);
		for(WebDriverEventListener listener : list){
			eventFiringWebDriver.register(listener);
			registeredMap.put(listener.getClass().getName(),listener);
		}
		return eventFiringWebDriver;
	}
	/**
	 * Wrap a specific web driver listener, anonymous implementation can not be registered
	 * @param eventFiringDriver
	 * @param listener
	 * @return
	 */
	public static WebDriver wrap(EventFiringWebDriver eventFiringDriver, WebDriverEventListener listener){
		// if listener is anonymous class
		if(listener.getClass().getSimpleName().equals("")){
			logger.info("Can not wrap anonymous webdriver listener class");
			return eventFiringDriver;
		}
		if(!registeredMap.containsKey(listener.getClass().getName())){
			eventFiringDriver.register(listener);
			registeredMap.put(listener.getClass().getName(),listener);
		}
		return eventFiringDriver;
	}
	/**
	 * Unwrap a specific web driver listener, anonymous implementation can not be used to find registered listener
	 * @param eventFiringDriver
	 * @param listener
	 * @return
	 */
	public static WebDriver unwrap(EventFiringWebDriver eventFiringDriver, WebDriverEventListener listener){
		// if listener is anonymous class
		if(listener.getClass().getSimpleName().equals("")){
			logger.info("Can not unwrap anonymous webdriver listener class");
			return eventFiringDriver;
		}
		if(registeredMap.containsKey(listener.getClass().getName())){
			eventFiringDriver.unregister(registeredMap.get(listener.getClass().getName()));
			registeredMap.remove(listener.getClass().getName());
		}
		return eventFiringDriver;
	}
	
	public enum BROWSER{
		FIREFOX("FireFox"),
		CHROME("Chrome"),
		IE("InternetExplorer"),
		HTMLUNIT("HtmlUnit");
		
		final String name;		
		BROWSER(String name){
			this.name = name;
		}	
		public String getName(){
			return this.name;
		}
	}

}
